#include "AppUtilPrecompiled.hxx"
#include "AppUtilAir.hxx"

using namespace HerzeleidLoggingNamespaceImpl;

#define TaskCancellationRequested concurrency::is_task_cancellation_requested
#define CancelCurrentTask concurrency::cancel_current_task

void Air::Print( _In_ AirTraits::AirPrintMessagesOption eOption )
{
	switch (eOption)
	{
	case AirTraits::AirPrintMessagesOption::PrintAllIfPresent:
		m_MessagePrinting.then( [this] ( bool bCancelled )
		{
			if ( !bCancelled )
			{
				while( !m_MessageQueue.empty() )
				{
					if ( TaskCancellationRequested() )
					{
						CancelCurrentTask();
						return( true );
					}
					else
					{
						PrintNext();
					}
				}
			}

			return( bCancelled );
		}); break;
	case AirTraits::AirPrintMessagesOption::PrintUntilQuit:
		m_MessagePrinting.then( [this] ( bool bCancelled )
		{
			if ( !bCancelled )
			{
				while( true )
				{
					if ( TaskCancellationRequested() )
					{
						CancelCurrentTask();
						return( true );
					}
					else
					{
						PrintNext();
					}
				}
			}

			return( bCancelled );
		}); break;
	case AirTraits::AirPrintMessagesOption::PrintOneIfPresent:
		if (!m_MessageQueue.empty())
		{
			PrintNextAsync();
		} break;
	}
}

Air::Air()
{
	Restart();

#if defined(REIGN_DESKTOP)

	// Buffer to write the default file path into. 
	Char wszPersonalFolderPath[MAX_PATH];

	// Get the default file path. 
	HRESULT hr = SHGetFolderPathW(
		0,  CSIDL_PERSONAL, 0, SHGFP_TYPE_CURRENT,
		_Outptr_opt_result_nullonfailure_ wszPersonalFolderPath
		);

	String16 wszPath = wszPersonalFolderPath;
	wszPath.append( L"\\Application\\" );
	auto Status = CreateDirectoryW( 
		wszPath.data(), nullptr 
		);

	wszPath.append( L"Logging\\" );
	Status = CreateDirectoryW( 
		wszPath.data(), nullptr 
		);

	wszPath.append( L"Desktop\\" );
	Status = CreateDirectoryW( 
		wszPath.data(), nullptr 
		);

	SYSTEMTIME Time;
	GetSystemTime(&Time);

	std::wstringstream wss; 
	wss.clear(); 
	wss << L"Air-Time-" << 
		Time.wHour << L"-" << 
		Time.wMinute << L"-" << 
		Time.wSecond << L"-Date-" << 
		Time.wDay << L"-" << 
		Time.wMonth << L"-" << 
		Time.wYear << L".Log.txt\0"; 
	wss.flush();

	CREATEFILE2_EXTENDED_PARAMETERS Params = { 0 };
	Params.dwSize = sizeof(CREATEFILE2_EXTENDED_PARAMETERS);
	Params.dwFileAttributes = FILE_ATTRIBUTE_NORMAL;
	Params.dwFileFlags = FILE_FLAG_SEQUENTIAL_SCAN;
	Params.dwSecurityQosFlags = SECURITY_ANONYMOUS;
	Params.lpSecurityAttributes = nullptr;
	Params.hTemplateFile = nullptr;

	m_FileWriteHandle.Attach(CreateFile2(
		wss.str().data(), // get file path
		GENERIC_WRITE, // write access only
		0, // non-shared access
		CREATE_ALWAYS, // get file handle anyway
		&Params // file attributes
		));

	if ( m_FileWriteHandle.Get() == INVALID_HANDLE_VALUE )
	{
		// Notify the library user that the logger wont 
		// write to file. 
		PrintToDebugWindowOnly8( 
			"Failed to get file handle." 
			);

		// Throws in ctors is are suppress for now 
		/*throw StandardError( 
			"Failed to get file handle."
			);*/
	}

	Grab( L"Application Air on Desktop:" );
	PrintNextAsync();

#else

	// The DocumentsLibrary access is required. 
	Windows::Storage::StorageFolder^ TempFolder = nullptr;

	try
	{
		TempFolder = Windows::Storage::KnownFolders::DocumentsLibrary;
	}
	catch( Platform::FailureException^ Failure )
	{
		PrintToDebugWindowOnly16( Failure->Message->Data() );
		m_FileWriteHandle.Attach(INVALID_HANDLE_VALUE);
		return;
	}
	catch( Platform::Exception^ Failure )
	{
		PrintToDebugWindowOnly16( Failure->Message->Data() );
		m_FileWriteHandle.Attach(INVALID_HANDLE_VALUE);
		return;
	}

	auto CreateFolderTask = concurrency::create_task(TempFolder->CreateFolderAsync( 
		L"Application", Windows::Storage::CreationCollisionOption::OpenIfExists 
		)).then( [this]( Windows::Storage::StorageFolder^ HerzeleidFolder )
		{
			return HerzeleidFolder->CreateFolderAsync( 
				L"Logging", Windows::Storage::CreationCollisionOption::OpenIfExists 
				);
		}).then( [this]( Windows::Storage::StorageFolder^ MetroLoggingFolder )
		{
#ifndef REIGN_PHONE // HERZELEID_METRO
			return MetroLoggingFolder->CreateFolderAsync( 
				L"Metro", Windows::Storage::CreationCollisionOption::OpenIfExists 
				);
#else // REIGN_PHONE
			return MetroLoggingFolder->CreateFolderAsync( 
				L"Phone", Windows::Storage::CreationCollisionOption::OpenIfExists 
				);
#endif // !REIGN_PHONE
		});

	auto InitTask = CreateFolderTask.then( [this]( Windows::Storage::StorageFolder^ HerzeleidFolder )
	{
		Platform::String ^FileName;
		{
			SYSTEMTIME Time;
			GetSystemTime(&Time);

			FileName = ref new Platform::String( L"Air-Time-" );
			FileName += Time.wHour;		FileName += L"-";
			FileName += Time.wMinute;	FileName += L"-";
			FileName += Time.wSecond;	FileName += L"-Date-";
			FileName += Time.wDay;		FileName += L"-";
			FileName += Time.wMonth;	FileName += L"-";
			FileName += Time.wYear;		FileName += L".Log.txt";
		}

		return HerzeleidFolder->CreateFileAsync( 
			FileName, Windows::Storage::CreationCollisionOption::GenerateUniqueName 
			);
	}).then( [this]( Windows::Storage::StorageFile^ LogFile )
	{
		CREATEFILE2_EXTENDED_PARAMETERS Params = { 0 };
		Params.dwSize = sizeof(CREATEFILE2_EXTENDED_PARAMETERS);
		Params.dwFileAttributes = FILE_ATTRIBUTE_NORMAL;
		Params.dwFileFlags = FILE_FLAG_SEQUENTIAL_SCAN;
		Params.dwSecurityQosFlags = SECURITY_ANONYMOUS;
		Params.lpSecurityAttributes = nullptr;
		Params.hTemplateFile = nullptr;

		m_FileWriteHandle.Attach(CreateFile2(
			LogFile->Path->Data(), // get file path
			GENERIC_WRITE, // write access only
			0, // non-shared access
			CREATE_ALWAYS, // get file handle anyway
			&Params // file attributes
			));

		if ( m_FileWriteHandle.Get() == INVALID_HANDLE_VALUE )
		{
			// This exception will raise later in ctor and will be quitly 
			// suppressed. The message (if any) will be shown in debug window. 
			throw ref new Platform::AccessDeniedException( 
				L"Failed to get file handle."
				);
		}

#ifndef REIGN_PHONE // HERZELEID_METRO
		Grab( L"Application Air on Metro: \n" );
#else // REIGN_PHONE
		Grab( L"Application Air on Phone: \n" );
#endif // !REIGN_PHONE
		PrintNextAsync();
	});

	try
	{
		InitTask.wait();
		InitTask.get();
	}
	// This one is expected, but the others should  raise in caller function. 
	catch( Platform::AccessDeniedException^ Failure )
	{
		PrintToDebugWindowOnly16( 
			Failure->Message->Data() 
			);
	}

#endif
}

Air::~Air()
{
	Await();
}

#ifndef REIGN_DESKTOP
void Air::Grab( _In_z_ Platform::String^ MessageString )
{
	m_MessageCapturing.then( [this, MessageString] ( bool bCancelled )
	{ // For the ref classes its quit valid to path the pointer as they have ref counters 
		m_MessageQueue.push( MessageString->Data() );
	});
}
#else
void Air::GrabWithArgs(  
	_Printf_format_string_ Char const *wszFormat, // Message format
	_Printf_format_string_params_(ppArgs) ... // Message args
	)
{
	// Msg buffer.
	Char aMsg16[MAX_PATH];
	// Fill the msg buffer out.
	va_list szArgs16; va_start( szArgs16, wszFormat );
	vswprintf_s( aMsg16, wszFormat, szArgs16 );
	va_end( szArgs16 ); // free args 

	// Append compiled message to the queue. 
	Grab( aMsg16 ); 
}
#endif

void Air::Grab( _In_z_ Air::Message wszMessage )
{
	m_MessageCapturing.then( [this, wszMessage]( bool bCancelled )
	{ // Here we should copy the whole message as fast as possible before it is destructed. 
		if (!bCancelled)
		{
			if ( TaskCancellationRequested() )
			{
				PrintToDebugWindowOnly8( 
					"Cancelling message capturing task." 
					);

				CancelCurrentTask();
				return( true );
			}
			else
			{
				m_MessageQueue.push( wszMessage );
			}
		}

		return( bCancelled );
	});
}

int Air::PrintNext()
{
	Message Text;
	if ( m_MessageQueue.try_pop( Text ))
	{
		OutputDebugStringW( Text.data() );

		if ( m_FileWriteHandle.Get() == INVALID_HANDLE_VALUE )
		{
			return( 0 );
		}

		SYSTEMTIME Time;
		GetSystemTime(&Time);
		std::wstringstream wss; wss.clear(); wss.flush();
		wss << Time.wHour << L":" << 
			Time.wMinute << L":" << 
			Time.wSecond << L":" << 
			Time.wMilliseconds << L"\t" << 
			// + windows specific end-line chars
			Text.data() << L"\r\n\0"; 
		wss.flush();
		Text = wss.str();

		DWORD dwNumBytesWritten = 0;
		DWORD BufferLength = static_cast<DWORD>(wss.str().size() * sizeof(Char));
		void const *BufferPtr = Text.data();

		if (!WriteFile( m_FileWriteHandle.Get(), BufferPtr, BufferLength, &dwNumBytesWritten, nullptr ) || // We have had written something, 
			(dwNumBytesWritten != (BufferLength))) // but it wasnt exactly what we wanted
		{
 #ifdef REIGN_DESKTOP
			throw StandardError( 
				"Failed to write buffer to file." 
				);
 #else // HERZELEID_METRO
			throw ref new Platform::FailureException( 
				L"Failed to write buffer to file." 
				);
 #endif // REIGN_DESKTOP
		};

		return( (int)dwNumBytesWritten );
	};

	return( -1 );
}

void Air::PrintNextAsync()
{
	m_MessagePrinting.then( [this] ( bool bCancelled )
	{
		if (!bCancelled)
		{
			if ( TaskCancellationRequested() )
			{
				PrintToDebugWindowOnly8( 
					"Cancelling message printing task." 
					);

				CancelCurrentTask();
				return( true );
			}
			else
			{
				PrintNext();
			}
		}

		return( bCancelled );
	});
}

void Air::Restart()
{
	// Recreate the cancellation token source for message grabbing. 
	m_CapturingCancellationSrc = AsyncCancellationTokenSource();
	// Recreate the cancellation token source for message printing. 
	m_PrintingCancellationSrc = AsyncCancellationTokenSource();

	// Reinitialize message capturing task chain. 
	m_MessageCapturing = create_task( 
		[]( Void ) -> Bool { return( false ); }, 
		m_CapturingCancellationSrc.get_token() 
		);
	// Reinitialize message dispatching task chain. 
	m_MessagePrinting = create_task( 
		[]( Void ) -> Bool { return( false ); }, 
		m_PrintingCancellationSrc.get_token() 
		);

	// Clean up all the pending messages. 
	m_MessageQueue.clear();
}

void Air::Cancel()
{
	// First, we should stop grabbing messages. 
	m_CapturingCancellationSrc.cancel(); // Cancel task chain.
	AutoDebugVariable( CapturingTaskStatus ) m_MessageCapturing.wait(); // Wait until the task is cancelled.

	// Then, we should terminate message dipatching task chain. 
	m_PrintingCancellationSrc.cancel(); // Cancel task chain.
	AutoDebugVariable( DispatchingTaskStatus ) m_MessagePrinting.wait(); // Wait until the task is cancelled.

	// TODO: Debug printout
	Restart();
}

void Air::Await()
{
	try
	{
		// First, we should stop grabbing messages. 
		AutoDebugVariable( CapturingTaskStatus ) m_MessageCapturing.wait(); // Wait until all the user messages are enqueued
		AutoDebugVariable( CapturingTaskResult ) m_MessageCapturing.get(); // Get results 

		// Then, we should terminate message print task chain. 
		AutoDebugVariable( DispatchingTaskStatus ) m_MessagePrinting.wait(); // Wait until print task chain is terminated
		AutoDebugVariable( DispatchingTaskResult ) m_MessagePrinting.get(); // Get results

		// TODO: Debug printout
	}
	catch( AsyncActionCanceledError const &Failure )
	{
		// Most likely, we wont reach this point, until the get() function is called. 
		PrintToDebugWindowOnly8(Failure.what());
	}
	catch( StandardError const &Failure )
	{
		// The exceptions, that were raised while the task execution should appear here. 
		// And we want to know "what()" exactly happend. 
		PrintToDebugWindowOnly8(Failure.what());
	}

	// Display all the pending messages immediately. 
	while( !m_MessageQueue.empty() )
	{
		PrintNext();
	}
}
